package com.lzx.hbh_system.log;

import cn.hutool.core.util.URLUtil;
import com.lzx.hbh_system.bo.user.UseroperaInfo;
import com.lzx.hbh_system.dto.UserDto;
import com.lzx.hbh_system.util.*;
import com.lzx.hbh_system.util.responseEntity.ResponseView;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.Date;
import java.util.Objects;

@Slf4j
@Aspect
@Component
public class SysLogAspect {
    // 初始化日志记录实体类
    private UseroperaInfo useroperaInfo = new UseroperaInfo();
    // 设置检测字符数组
    static final String[] addCheckArray = {"add","Add","insert","Insert","put","Put"};
    static final String[] dropCheckArray = {"delete","Delete","drop","Drop"};
    static final String[] modifyCheckArray = {"modify","Modify","update","Update"};
    @Autowired
    private ApplicationContext applicationContext;
    @Autowired
    private RedisUtils redisUtils;
    @Autowired
    private ModelMapper modelMapper;

    @Pointcut("@annotation(com.lzx.hbh_system.log.SysLog)")
    public void sysLogAspect(){

    }
    @Before(value = "sysLogAspect()")
    public void recordLog(JoinPoint joinPoint) throws Throwable{
        // 开始时间
        long beginTime = new Date().getTime();
        System.out.println("日志记录开始时间："+beginTime);
        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
        String token = request.getHeader("token");
        String username = JWTUtil.getUsername(token);
        UserDto userDto = (UserDto) redisUtils.getObject(username);
        String userCode = userDto.getUserCode();
        String requestPath = URLUtil.getPath(request.getRequestURI());
        String[] PathArray = requestPath.split("/");
        // 访问目标方法的参数 可动态改变参数值
        Object[] args = joinPoint.getArgs();
        // 设置操作日志属性值
        // 设置记录用户编号
        useroperaInfo.setUserCode(userCode);
        // 设置记录用户名
        useroperaInfo.setUserName(username);
        // 设置请求URL参数
        useroperaInfo.setRequestPath(requestPath);
        // 设置操作记录开始时间参数
        useroperaInfo.setOperaTime(TurnDateToStringUtil.getDateTime());
        // 设置请求IP地址参数
        useroperaInfo.setRequestIp(RequestIPUtil.getIP(request));
        // 设置请求方式
        useroperaInfo.setRequestMethodType(request.getMethod());
        // 根据请求字段判断 并设置操作类型代号
        useroperaInfo.setOperaType(checkUripath(PathArray));
        // 设置请求执行方法名称
        useroperaInfo.setRequestMethodName(joinPoint.getSignature().getName());
        // 设置方法执行结束时间
        useroperaInfo.setOperaEndTime(TurnDateToStringUtil.getDateTime());
        // 设置请求参数
        useroperaInfo.setRequestParams(Arrays.toString(args));
        // 设置类路径
        useroperaInfo.setClassPath(joinPoint.getTarget().getClass().getName());
        // 设置日志记录名称
        useroperaInfo.setOperaTitle(LogUtil.getControllerMethodDescription(joinPoint));
        // 设置操作日志记录内容
        useroperaInfo.setOperaContent("用户名：["+username+"],进行了：["+LogUtil.getControllerMethodDescription(joinPoint)+"]");
        // 设置操作执行耗费时间
        useroperaInfo.setConsumingTime(new Date().getTime() - beginTime);
        long time = new Date().getTime() - beginTime;
        System.out.println("结束时间："+time);
        // 设置默认有效标识 1
        useroperaInfo.setValiFlag("1");
    }

    /**
     * 返回通知
     * @param returnObj
     */
    @AfterReturning(returning = "returnObj",pointcut = "sysLogAspect()")
    public void doAfterReturning(Object returnObj){
        // 处理完请求 返回内容 映射回参响应实体
        ResponseView r = modelMapper.map(returnObj, ResponseView.class);
        useroperaInfo.setType(1);
        if (r.getRespOutMsgHeader().getRespCode().equals(200)){
            // 正常
            useroperaInfo.setType(1);
            useroperaInfo.setOperaResult("1");
        }else{
            useroperaInfo.setType(2);
            useroperaInfo.setOperaResult("0");
            useroperaInfo.setErrorMsg(r.getRespOutMsgHeader().getMsg());
        }
        // 发布事件
        applicationContext.publishEvent(new SysLogEvent(useroperaInfo));
    }

    /**
     * 异常通知
     * @param e
     */
    @AfterThrowing(pointcut = "sysLogAspect()",throwing = "e")
    public void doAfterThrowable(Throwable e){
        // 异常类型
        useroperaInfo.setType(2);
        // 异常信息描述 --
        useroperaInfo.setExDesc(e.getMessage());
        // 异常信息详情 暂时由于退栈异常信息过长会超过存储长度所以 不能完全存储异常信息 Data too long for column 'ex_detail'
        useroperaInfo.setExDetail(getStackTrace(e));
        // 发布事件
        applicationContext.publishEvent(new SysLogEvent(useroperaInfo));
    }

    /**
     *  获取退栈异常信息
     * @param throwable
     * @return
     */
    public static String getStackTrace(Throwable throwable){
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        try {
            throwable.printStackTrace(pw);
            int errorLength = pw.toString().length();
            int subendIndex = (int) (errorLength);
            return sw.toString().substring(0,subendIndex);
        } finally {
            pw.close();
        }
    }
    /**
     * 操作类型代号判断循环体 获取操作类型代号
     * @param PathArray
     * @return
     */
    public String checkUripath(String[] PathArray) {
        String flag_str = PathArray[2];
        // 循环体判断
        for (String s : addCheckArray) {
            if (flag_str.contains(s)) {
                return "01";
            }
        }
        for (String s : modifyCheckArray) {
            if (flag_str.contains(s)) {
                return "03";
            }
        }
        for (String s : dropCheckArray) {
            if (flag_str.contains(s)) {
                return "02";
            }
        }
        return "00";
    }
}
